﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace JDT_NET.Delaunay_triangulation
{
    /**
 * This class represents a 3D triangle in a Triangulation! 
 *
 */

    public class Triangle_dt : ICloneable
    {
        internal Point_dt a, b, c;
        internal Triangle_dt abnext, bcnext, canext;
        Circle_dt circum;
        internal int _mc = 0; // modcounter for triangulation fast update.

        internal bool halfplane = false; // true iff it is an infinite face.

        //	public boolean visitflag;
        internal bool _mark = false;   // tag - for bfs algorithms

        //	private static boolean visitValue=false;
        public static int _counter = 0, _c2 = 0;

        //public int _id;
        /** constructs a triangle form 3 point - store it in counterclockwised order.*/
         public Triangle_dt(Point_dt A, Point_dt B, Point_dt C)
         {
            //visitflag=visitValue;
		    a = A;
		    int res = C.pointLineTest(A,B);
		    if ( (res <= Point_dt.LEFT) ||
		         (res == Point_dt.INFRONTOFA) ||
		         (res == Point_dt.BEHINDB) )
            {
			    b = B;
			    c = C;
		    }
		    else 
            {  // RIGHT
			    Console.WriteLine("Warning, ajTriangle(A,B,C) "+  "expects points in counterclockwise order.");
                Console.WriteLine("" + A + B + C);
			    b=C;
			    c=B;
		    }
		    circumcircle();
		    //_id = _counter++;
		    //_counter++;_c2++;
		    //if(_counter%10000 ==0) System.out.println("Triangle: "+_counter);
	    }

        /**
         * creates a half plane using the segment (A,B).
         * @param A
         * @param B
         */
        public Triangle_dt(Point_dt A, Point_dt B)
        {
            //		visitflag=visitValue;
            a = A;
            b = B;
            halfplane = true;
            //		_id = _counter++;
        }
        /*	protected void finalize() throws Throwable{
                super.finalize();
                _counter--;
            } */

        /** 
         * remove all pointers (for debug)
         */
        //public void clear() {
        //	this.abnext = null; this.bcnext=null; this.canext=null;}


        /*
         * 
         * returns true iff this triangle is actually a half plane.
         */
        public bool isHalfplane()
        {
            return this.halfplane;
        }

        /**
         * returns the first vertex of this triangle.
         */
        public Point_dt p1()
        {
            return a;
        }

        /**
         * returns the second vertex of this triangle.
         */
        public Point_dt p2()
        {
            return b;
        }

        /**
         * returns the 3th vertex of this triangle.
         */
        public Point_dt p3()
        {
            return c;
        }

        /**
         * returns the consecutive triangle which shares this triangle p1,p2 edge. 
         */
        public Triangle_dt next_12()
        {
            return this.abnext;
        }

        /**
         * returns the consecutive triangle which shares this triangle p2,p3 edge. 
         */
        public Triangle_dt next_23()
        {
            return this.bcnext; 
        }

        /**
         * returns the consecutive triangle which shares this triangle p3,p1 edge. 
         */
        public Triangle_dt next_31()
        {
            return this.canext;
        }


        internal void switchneighbors(Triangle_dt Old, Triangle_dt New)
        {
            if ( abnext == Old )
                abnext = New;
            else if ( bcnext == Old ) 
                bcnext = New;
            else if ( canext == Old )
                canext=New;
            else 
                Console.WriteLine( "Error, switchneighbors can't find Old." );
         }

        Triangle_dt neighbor(Point_dt p)
        {
            if ( a==p ) return canext;
            if ( b==p ) return abnext;
            if ( c==p ) return bcnext;
            Console.WriteLine( "Error, neighbors can't find p: " + p );
            return null;
        }

        internal Circle_dt circumcircle()
        {

            double u = ((a.x - b.x) * (a.x + b.x) + (a.y - b.y) * (a.y + b.y)) / 2.0f;
            double v = ((b.x - c.x) * (b.x + c.x) + (b.y - c.y) * (b.y + c.y)) / 2.0f;
            double den = (a.x - b.x) * (b.y - c.y) - (b.x - c.x) * (a.y - b.y);
            if (den == 0) // oops, degenerate case
                circum = new Circle_dt(a, Double.PositiveInfinity);
            else
            {
                Point_dt cen = new Point_dt((u * (b.y - c.y) - v * (a.y - b.y)) / den,
                                           (v * (a.x - b.x) - u * (b.x - c.x)) / den);
                circum = new Circle_dt(cen, cen.distance2(a));
            }
            return circum;
        }

        internal bool circumcircle_contains(Point_dt p)
        {
            return circum.Radius() > circum.Center().distance2(p);
        }

        public String toString()
        {
            string res = ""; //+_id+") ";
            res += a.toString() + b.toString();
            if (!halfplane)
                res += c.toString();
            // res +=c.toString() +"   | "+abnext._id+" "+bcnext._id+" "+canext._id;
            return res;
        }

        /**
         * determinates if this triangle contains the point p.
         * @param p the query point
         * @return true iff p is not null and is inside this triangle (Note: on boundary is considered inside!!).
         */
        public bool contains(Point_dt p)
        {
            bool ans = false;
            if (this.halfplane || p == null) return false;

            if ((p.x == a.x && p.y == a.y) || (p.x == b.x && p.y == b.y) || (p.x == c.x && p.y == c.y)) return true;
            int a12 = p.pointLineTest(a, b);
            int a23 = p.pointLineTest(b, c);
            int a31 = p.pointLineTest(c, a);

            if ((a12 == Point_dt.LEFT && a23 == Point_dt.LEFT && a31 == Point_dt.LEFT) ||
            (a12 == Point_dt.RIGHT && a23 == Point_dt.RIGHT && a31 == Point_dt.RIGHT) ||
            (a12 == Point_dt.ONSEGMENT || a23 == Point_dt.ONSEGMENT || a31 == Point_dt.ONSEGMENT))
                ans = true;

            return ans;
        }

        /**
         * compute the Z value for the X,Y values of q. <br />
         * assume this triangle represent a plane --> q does NOT need to be contained
         * in this triangle.
         * 
         * @param q query point (its Z value is ignored).
         * @return the Z value of this plane implies by this triangle 3 points.
         */
        public double z_value(Point_dt q)
        {
            if (q == null || this.halfplane) 
                throw new Exception("*** ERR wrong parameters, can't approximate the z value ..***: " + q);

            /* incase the query point is on one of the points */
            if (q.x == a.x & q.y == a.y) return a.z;
            if (q.x == b.x & q.y == b.y) return b.z;
            if (q.x == c.x & q.y == c.y) return c.z;

            /* 
            *  plane: aX + bY + c = Z:
            *  2D line: y= mX + k
            *  
            */
            double X = 0, x0 = q.x, x1 = a.x, x2 = b.x, x3 = c.x;
            double Y = 0, y0 = q.y, y1 = a.y, y2 = b.y, y3 = c.y;
            double Z = 0, m01 = 0, k01 = 0, m23 = 0, k23 = 0;

            // 0 - regular, 1-horisintal , 2-vertical.
            int flag01 = 0;
            if (x0 != x1)
            {
                m01 = (y0 - y1) / (x0 - x1);
                k01 = y0 - m01 * x0;
                if (m01 == 0) flag01 = 1;
            }
            else
            { // 2-vertical.
                flag01 = 2;//x01 = x0
            }
            int flag23 = 0;
            if (x2 != x3)
            {
                m23 = (y2 - y3) / (x2 - x3);
                k23 = y2 - m23 * x2;
                if (m23 == 0) flag23 = 1;
            }
            else
            { // 2-vertical.
                flag23 = 2;//x01 = x0
            }

            if (flag01 == 2)
            {
                X = x0;
                Y = m23 * X + k23;
            }
            else
            {
                if (flag23 == 2)
                {
                    X = x2;
                    Y = m01 * X + k01;
                }
                else
                {  // regular case 
                    X = (k23 - k01) / (m01 - m23);
                    Y = m01 * X + k01;

                }
            }
            double r = 0;
            if (flag23 == 2) { r = (y2 - Y) / (y2 - y3); } else { r = (x2 - X) / (x2 - x3); }
            Z = b.z + (c.z - b.z) * r;
            if (flag01 == 2) { r = (y1 - y0) / (y1 - Y); } else { r = (x1 - x0) / (x1 - X); }
            double qZ = a.z + (Z - a.z) * r;
            return qZ;
        }

        /**
         * compute the Z value for the X,Y values of q.
         * assume this triangle represent a plane --> q does NOT need to be contained
         * in this triangle.
         *   
         * @param x  x-coordinate of the query point.
         * @param y  y-coordinate of the query point.
         * @return z (height) value approximation given by the triangle it falls in.
         * 
         */
        public double z(double x, double y)
        {
            return z_value(new Point_dt(x, y));
        }
        /**
         * compute the Z value for the X,Y values of q.
         * assume this triangle represent a plane --> q does NOT need to be contained
         * in this triangle.
         *   
         * @param q query point (its Z value is ignored).
         * @return q with updated Z value.
         * 
         */
        public Point_dt z(Point_dt q)
        {
            double z = z_value(q);
            return new Point_dt(q.x, q.y, z);
        }

        public object Clone()
        {
            Triangle_dt cloned = new Triangle_dt(a,b,c);
            cloned._mark = _mark;
            cloned._mc = _mc;
            cloned.abnext = abnext;
            cloned.bcnext = bcnext;
            cloned.canext = canext;
            cloned.halfplane = halfplane;
            cloned.canext = canext;
            cloned.circum = circum;
            return cloned;
        }

        public override bool Equals(object obj)
        {
            var t = (Triangle_dt)obj;
            return (this.p1() == t.p1() && this.p2() == t.p2() && this.p3() == t.p3());
        }

        public override int GetHashCode()
        {            
            return a.GetHashCode() + b.GetHashCode();
        }
    }

}
